// TODO: 支持原地读写速度测试、支持全盘读出及输出映像
#include "diskTest.h"
static off_t break_point_sinletask = -1;
static size_t head_singletask = 0;
off_t *break_point = &break_point_sinletask;
size_t *head = &head_singletask;

// It is seem something wrong in asm
static char get_long(uint64_t val) {
        // 8 16 24 32 40 48 56 64
        uint64_t UINT24_MAX = (uint64_t)UINT16_MAX << 8 | UINT8_MAX;
        uint64_t UINT40_MAX = (uint64_t)UINT32_MAX << 8 | UINT8_MAX;
        uint64_t UINT48_MAX = (uint64_t)UINT40_MAX << 8 | UINT8_MAX;
        uint64_t UINT56_MAX = (uint64_t)UINT48_MAX << 8 | UINT8_MAX;
        if (val <= UINT8_MAX ) return 1;
        if (val <= UINT16_MAX) return 2;
        if (val <= UINT24_MAX) return 3;
        if (val <= UINT32_MAX) return 4;
        if (val <= UINT40_MAX) return 5;
        if (val <= UINT48_MAX) return 6;
        if (val <= UINT56_MAX) return 7;
        return 8;
}

// Make data static function
static uint64_t urand_pool[1024];
static int urand_pool_head = 1024;
static int64_t get_urand(FILE *urand) {
        if (urand_pool_head == 1024) {
                fread(urand_pool, sizeof(uint64_t), 1024, urand);
                urand_pool_head = 0;
        }
        return urand_pool[urand_pool_head++];
}
static int64_t get_seed_rand(FILE *_nil) {
        (void)_nil;
        uint64_t bin = ((uint64_t)mrand48() << 32) | (mrand48() & UINT32_MAX);
        // TODO: Add be, se mode
        bin = htobe64(bin);
        return bin;
}
static uint64_t get_num(uint64_t code, uint64_t where) {
        where = be64toh(where);
        where++;
        if (where > code)
                return 0;
        where = htobe64(where);
        return where;
}

// Writing Fuction
// TODO: if fail to write, timeout is needen to log out
// TODO: Add file io pool
void write_random(void) {
        FILE *urand = NULL;
        typeof(get_urand) *get_rand;
        if (!opt.seeded) {
                urand = fopen64("/dev/random", "rb");
                get_rand = get_urand;
        } else {
                srand48(opt.seed);
                get_rand = get_seed_rand;
        }

        *head = opt.start;
        uint64_t bin = get_rand(urand);
        while (*head + 8 <= opt.end) {
                fbwrite(&bin, 8);
                bin = get_rand(urand);
                *head += 8;
        }
        fbwrite(&bin, opt.end - *head + 1);
        if (urand)
                fclose(urand);
}
void write_num(void) {
        char byte = 1;
        uint64_t code = 0xFF;
        if (opt.seeded) {
                code = opt.seed;
                byte = get_long(code);
        }

        *head = opt.start;
        uint64_t bin = 0;
        while (*head + byte <= opt.end) {
                fbwrite((char *)&bin + (8 - byte), byte);
                bin = get_num(code, bin);
                *head += byte;
        }
        fbwrite((char *)&bin + (8 - byte), opt.end - *head + 1);
}

// Test area, and if some pear in cmp, don't care.
#ifndef _GUI
#       define CMP_OUTPUT printf("%zd TO %zd Break!\n", *break_point, head)
#else // _GUI
struct break_collect *break_log_write;
#       define CMP_OUTPUT do {                                                                          \
                if (break_log_write->used == 1024) {                                                    \
                        struct break_collect *new_collecter = malloc_se(sizeof(struct break_collect));  \
                        memset(new_collecter, 0, sizeof(struct break_collect));                         \
                        break_log_write->next = new_collecter;                                          \
                        break_log_write = new_collecter;                                                \
                }                                                                                       \
                int obj_num = break_log_write->used;                                                    \
                break_log_write->log[obj_num].start = *break_point;                                     \
                break_log_write->log[obj_num].end = head;                                               \
                break_log_write->used ++;                                                               \
        } while (0)
#endif // _GUI
static void cmp_byte(uint64_t hope, uint64_t cmp, off_t head) {
        if (hope == cmp) {
                if (*break_point == -1)
                        return;
                CMP_OUTPUT;
                *break_point = -1;
        } else {
                if (*break_point == -1)
                        *break_point = head;
        }
}
static void cmp_flush(off_t head) {
        if (*break_point == -1)
                return;
        CMP_OUTPUT;
}
#undef CMP_OUTPUT

void test_num(void) {
        char byte = 1;
        uint64_t code = 0xFF;
        if (opt.seeded) {
                code = opt.seed;
                byte = get_long(code);
        }

        *head = opt.start;
        // NEW
        uint64_t hope = 0;
        while (*head + byte <= opt.end) {
                uint64_t bin = 0;
                fbread((char *)&bin + (8 - byte), byte);
                cmp_byte(hope, bin, *head);
                hope = get_num(code, hope);
                *head += byte;
        }
        uint64_t bin = hope;
        fbread((char *)&bin + (8 - byte), opt.end - *head + 1);
        cmp_byte(hope, bin, *head);
        cmp_flush(*head);
}
void test_seed(void) {
        srand48(opt.seed);
        *head = opt.start;
        uint64_t hope = get_seed_rand(NULL);
        while (*head + 8 <= opt.end) {
                uint64_t bin;
                fbread(&bin, 8);
                cmp_byte(hope, bin, *head);
                hope = get_seed_rand(NULL);
                *head += 8;
        }
        uint64_t bin = hope;
        fbread(&bin, opt.end - *head + 1);
        cmp_byte(hope, bin, *head);
        cmp_flush(*head);
}
